// Copyright (c) 2011 Blue Onion Software, All rights reserved
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Web.Caching;
using System.Web.Hosting;
using System.Web.UI;
using System.Web.UI.Design;
using System.Web.UI.WebControls;

namespace BlueOnionSoftware.Bloget
{
    /// <summary>
    /// Blog Rolls are lists of blogs to be displayed. The input is a Uri to a OPML file.
    /// </summary>
    [Designer(typeof (BlogetBlogRollDesigner))]
    public class BlogetBlogRoll : Bloget
    {
        private Uri _opmlUrl;
        private int _length;
        private bool _random;
        private ITemplate _blogRollTemplate;

        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue("blogroll.opml")]
        [Localizable(false)]
        public Uri Uri
        {
            get { return _opmlUrl; }
            set { _opmlUrl = value; }
        }

        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue("10")]
        [Localizable(false)]
        public int Length
        {
            get { return _length; }
            set { _length = (value >= 0) ? value : _length; }
        }

        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue("false")]
        [Localizable(false)]
        public bool Random
        {
            get { return _random; }
            set { _random = value; }
        }

        [Browsable(false)]
        [Description("Blog Roll template")]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof (BlogRollContainerControl))]
        public ITemplate BlogRollTemplate
        {
            get { return _blogRollTemplate; }
            set { _blogRollTemplate = value; }
        }

        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        protected override void CreateChildControls()
        {
            try
            {
                var outlines = GetOpml(_opmlUrl).Outlines;
                if (_blogRollTemplate == null)
                {
                    Controls.Add(new LiteralControl(@"<ul class=""blogroll-list"">"));
                }
                foreach (var outline in GetOutlines(outlines, _length, _random))
                {
                    if (_blogRollTemplate != null)
                    {
                        var blogRollContainer = new BlogRollContainerControl(this, Blog, outline);
                        _blogRollTemplate.InstantiateIn(blogRollContainer);
                        Controls.Add(blogRollContainer);
                    }
                    else
                    {
                        Controls.Add(new LiteralControl("<li>"));
                        var link = new HyperLink
                        {
                            NavigateUrl = (outline.XmlUrl != null) ? outline.XmlUrl.ToString() : string.Empty,
                            Text = outline.Title
                        };
                        Controls.Add(link);
                        Controls.Add(new LiteralControl("</li>" + Environment.NewLine));
                    }
                }
                if (_blogRollTemplate == null)
                {
                    Controls.Add(new LiteralControl("</ul>" + Environment.NewLine));
                }
                base.CreateChildControls();
            }

            catch (Exception ex)
            {
                Log.Exception(this, "CreateControls", ex);
            }
        }

        private static IEnumerable<OpmlOutline> GetOutlines(IList<OpmlOutline> outlines, int count, bool selectRandom)
        {
            Random randomGenerator = null;
            List<int> used = null;

            if (selectRandom)
            {
                randomGenerator = new Random();
                used = new List<int>();
            }

            count = (count <= 0) ? outlines.Count : Math.Min(count, outlines.Count);

            for (var i = 0; i < count; ++i)
            {
                var next = i;

                if (randomGenerator != null)
                {
                    next = randomGenerator.Next()%outlines.Count;

                    while (used.Contains(next))
                    {
                        next = (next + 1)%outlines.Count;
                    }

                    used.Add(next);
                }

                yield return outlines[next];
            }
        }

        private static Opml GetOpml(Uri opmlUrl)
        {
            return GetOpml(opmlUrl, 2000);
        }

        /// <summary>
        /// Gets an OPML file from a Uri
        /// </summary>
        /// <param name="opmlUrl">The opml URL.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        private static Opml GetOpml(Uri opmlUrl, int timeout)
        {
            if (opmlUrl == null)
            {
                return new Opml();
            }

            var opml = HostingEnvironment.Cache[opmlUrl.ToString()] as Opml;

            if (opml == null)
            {
                if (opmlUrl.Scheme == Uri.UriSchemeHttp || opmlUrl.Scheme == Uri.UriSchemeHttps)
                {
                    var request = (HttpWebRequest) WebRequest.Create(opmlUrl);
                    request.Timeout = timeout;
                    request.ReadWriteTimeout = timeout;

                    using (var response = (HttpWebResponse) request.GetResponse())
                    {
                        opml = (response.StatusCode == HttpStatusCode.OK)
                            ? Opml.Parse(response.GetResponseStream())
                            : new Opml();

                        if (opml.Outlines.Count != 0)
                        {
                            HostingEnvironment.Cache.Insert(opmlUrl.ToString(), opml, null,
                                                            DateTime.Now.AddHours(6), Cache.NoSlidingExpiration);
                        }
                    }
                }

                if (opmlUrl.Scheme == Uri.UriSchemeFile)
                {
                    var path = opmlUrl.LocalPath;

                    if (path.StartsWith("~", StringComparison.Ordinal))
                    {
                        path = HostingEnvironment.MapPath(path);
                    }

                    using (var fileStream = FileUtilities.OpenRead(path))
                    {
                        opml = Opml.Parse(fileStream);

                        using (var cacheDependency = new CacheDependency(opmlUrl.LocalPath))
                        {
                            if (opml.Outlines.Count != 0)
                            {
                                HostingEnvironment.Cache.Insert(opmlUrl.ToString(), opml, cacheDependency);
                            }
                        }
                    }
                }
            }

            return opml;
        }
    }

    /// <summary>
    /// ArchiveContainerControl defines what can be referenced in the Archive
    /// template
    /// </summary>
    public class BlogRollContainerControl : BlogContainerControl
    {
        private readonly OpmlOutline _outline;

        public BlogRollContainerControl(Bloget bloget, Blog blog, OpmlOutline outlineArg)
            : base(bloget, blog)
        {
            Throw.IfNull(outlineArg, "outlineArg");
            _outline = outlineArg;
        }

        public string BlogRollTitle
        {
            get { return _outline.Title ?? string.Empty; }
        }

        public Uri BlogRollXmlUrl
        {
            get { return _outline.XmlUrl; }
        }

        public Uri BlogRollHtmlUrl
        {
            get { return _outline.HtmlUrl; }
        }
    }

    /// <summary>
    /// Designer support exposes only the archive template
    /// </summary>
    internal class BlogetBlogRollDesigner : ControlDesigner
    {
        [SuppressMessage("Microsoft.Security", "CA2116:AptcaMethodsShouldOnlyCallAptcaMethods")]
        public override void Initialize(IComponent component)
        {
            base.Initialize(component);
            SetViewFlags(ViewFlags.TemplateEditing, true);
        }

        public override string GetDesignTimeHtml()
        {
            return "<span>Sample Blog Roll</span><br/><span>Sample Blog Roll</span>";
        }

        public override bool AllowResize
        {
            get { return true; }
        }

        [SuppressMessage("Microsoft.Security", "CA2116:AptcaMethodsShouldOnlyCallAptcaMethods")]
        public override TemplateGroupCollection TemplateGroups
        {
            get
            {
                var collection = new TemplateGroupCollection();
                var control = (Bloget) Component;
                var group = new TemplateGroup("Bloget");
                var template = new TemplateDefinition(this, "BlogRollTemplate", control, "BlogRollTemplate", true);
                group.AddTemplateDefinition(template);
                collection.Add(group);
                return collection;
            }
        }
    }
}